package com.scy.rsa;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;

import javax.crypto.Cipher;
import java.io.File;
import java.nio.charset.Charset;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * RSAdemo
 *
 * @Author: 马伟奇
 * @CreateTime: 2020-05-05
 * @Description:
 */
public class RSADemo {

    public static final String publicKeyFileName = "my.publicKey";
    public static final String privateKeyFileName = "my.privateKey";
    public static final String algorithm = "RSA";

    public static void main(String[] args) throws Exception {

        System.out.println("当前行号: " + Thread.currentThread().getStackTrace()[1].getLineNumber()); // 输出当前行号

        String plainText = "中国ab12";

        // 创建密钥对
        // KeyPairGenerator:密钥对生成器对象
        // 生成密钥对并保存在本地文件中
        generateKeyToFile(algorithm, publicKeyFileName, privateKeyFileName);

        // 读取私钥
        PrivateKey privateKey = getPrivateKey(privateKeyFileName, algorithm);
//        System.out.println("privateKey ----"+privateKey);
        // 读取公钥key
        PublicKey publicKey = getPublicKey(publicKeyFileName, algorithm);
//        System.out.println("publicKey----"+publicKey);


        String cipherTextEncryptByRsaPrivateKey = encryptRSAPrivateKey(algorithm, privateKey, plainText);
        System.out.println("私钥加密后的密文:" + cipherTextEncryptByRsaPrivateKey);

        String plainTextDecryptByRsaPubKey = decryptRSAPublicKey(algorithm, publicKey, cipherTextEncryptByRsaPrivateKey);
        System.out.println("公钥解密后的明文:" + plainTextDecryptByRsaPubKey);

        System.out.println("-----------------------------------------------------------");

        String cipherTextEncryptByRsaPublicKey = encryptRSAPublicKey(algorithm, publicKey, plainText);
        System.out.println("公钥加密后的密文:" + cipherTextEncryptByRsaPublicKey);
        String plainTextDecryptByRsaPrivateKey = decryptRSAPrivateKey(algorithm, privateKey, cipherTextEncryptByRsaPublicKey);
        System.out.println("私钥解密后的明文:" + plainTextDecryptByRsaPrivateKey);
        //如果拿私钥去解密一个不是由公钥加密的密文,它会报错的. javax.crypto.BadPaddingException: Decryption error
        System.out.println("如果拿私钥去解密一个不是由公钥加密的密文,它会报错的.报错信息如下:");
        String plainTextDecryptByRsaPrivateKey2 = decryptRSAPrivateKey(algorithm, privateKey, "cipherTextEncryptByRsaPublicKey");
        System.out.println("-------------" + plainTextDecryptByRsaPrivateKey2);

    }

    /**
     * 从文件中读取公钥字符串,并用于生成公钥对象
     *
     * @param publicPath 公钥路径
     * @param algorithm  算法
     * @return
     */
    public static PublicKey getPublicKey(String publicPath, String algorithm) throws Exception {
        String publicKeyString = FileUtils.readFileToString(new File(publicPath), Charset.defaultCharset());
        // 创建key的工厂
        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
        // 创建公钥规则
        // 查看该类的javaDoc说明,X509EncodedKeySpec 该类用于构建公钥对象,只需要传递公钥的字节数组就可以了
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyString));
        return keyFactory.generatePublic(keySpec);
    }

    /**
     * 从文件中读取私钥字符串,并用于生成私钥对象
     *
     * @param priPath   私钥的路径
     * @param algorithm 算法
     * @return 返回私钥的key对象
     */
    public static PrivateKey getPrivateKey(String priPath, String algorithm) throws Exception {

        String privateKeyString = FileUtils.readFileToString(new File(priPath), Charset.forName(System.getProperty("file.encoding")));
        // 创建key的工厂
        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
        // 创建私钥key的规则
        // 查看该类的javaDoc说明,PKCS8EncodedKeySpec该类用于构建私钥对象,只需要传递私钥的字节数组就可以了
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyString));
        // 返回私钥对象
        return keyFactory.generatePrivate(keySpec);
    }


    /**
     * 使用公钥解密私钥加密的数据
     *
     * @param algorithm                          : 算法
     * @param cipherTextEncryptedByRsaPrivateKey : 私钥加密后的密文
     * @param publicKey                          : 公钥
     * @return : 明文
     * @throws Exception
     */
    public static String decryptRSAPublicKey(String algorithm, Key publicKey, String cipherTextEncryptedByRsaPrivateKey) throws Exception {
        // 创建加密对象
        Cipher cipher = Cipher.getInstance(algorithm);
        // 公钥解密
        cipher.init(Cipher.DECRYPT_MODE, publicKey);
        // 使用base64进行转码
        byte[] originalPrivateKeyByteArray = Base64.decodeBase64(cipherTextEncryptedByRsaPrivateKey);

        // 进行解密操作
        byte[] bytes1 = cipher.doFinal(originalPrivateKeyByteArray);
        return new String(bytes1);
    }

    /**
     * 私钥解密公钥加密后的数据
     *
     * @param algorithm                         : 算法
     * @param cipherTextEncryptedByRsaPublicKey : 公钥加密后的密文
     * @param privateKey                        : 私钥
     * @return : 明文
     * @throws Exception
     */
    public static String decryptRSAPrivateKey(String algorithm, Key privateKey, String cipherTextEncryptedByRsaPublicKey) throws Exception {
        // 创建加密对象
        Cipher cipher = Cipher.getInstance(algorithm);
        // 私钥解密
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        // 使用base64进行转码
        byte[] decode = Base64.decodeBase64(cipherTextEncryptedByRsaPublicKey);

        byte[] bytes1 = cipher.doFinal(decode);
        return new String(bytes1);
    }


    /**
     * 使用私钥加密明文数据
     *
     * @param algorithm  : 算法
     * @param plainText  : 原文
     * @param privateKey : 私钥
     * @return : 私钥加密后的密文
     * @throws Exception
     */
    public static String encryptRSAPrivateKey(String algorithm, Key privateKey, String plainText) throws Exception {
        // 创建加密对象
        Cipher cipher = Cipher.getInstance(algorithm);
        // 对加密进行初始化
        // 第一个参数：加密的模式
        // 第二个参数：你想使用公钥加密还是私钥加密
        // 我想使用私钥进行加密
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
        // 使用私钥进行加密
        byte[] bytes = cipher.doFinal(plainText.getBytes());

        return Base64.encodeBase64String(bytes);
    }


    /**
     * 使用公钥加密明文数据
     *
     * @param algorithm : 算法
     * @param plainText : 原文
     * @param publicKey : 公钥
     * @return : 私钥加密后的密文
     * @throws Exception
     */
    public static String encryptRSAPublicKey(String algorithm, Key publicKey, String plainText) throws Exception {
        // 创建加密对象
        Cipher cipher = Cipher.getInstance(algorithm);
        // 对加密进行初始化
        // 第一个参数：加密的模式
        // 第二个参数：你想使用公钥加密还是私钥加密
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        // 使用私钥进行加密
        byte[] bytes = cipher.doFinal(plainText.getBytes());
//        return Base64.encode(bytes);
        return Base64.encodeBase64String(bytes);
    }


    /**
     * 把公钥和私钥保存到根目录的文件中
     *
     * @param algorithm 算法
     * @param pubPath   公钥路径
     * @param priPath   私钥路径
     */
    private static void generateKeyToFile(String algorithm, String pubPath, String priPath) throws Exception {

        File privateFile = new File(priPath);
        File publicFile = new File(pubPath);

        // 如果文件存在,直接返回不用再创建,提高效率
        if (privateFile.exists()) {
            if (publicFile.exists()) {
                return;
            }
        }


        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
        // 生成密钥对
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        // 生成私钥
        PrivateKey privateKey = keyPair.getPrivate();
        // 生成公钥
        PublicKey publicKey = keyPair.getPublic();
        // 获取私钥的字节数组
        byte[] privateKeyEncoded = privateKey.getEncoded();
        // 获取公钥字节数组
        byte[] publicKeyEncoded = publicKey.getEncoded();
        // 使用base64进行编码
        String privateEncodeString = Base64.encodeBase64String(privateKeyEncoded);
        String publicEncodeString = Base64.encodeBase64String(publicKeyEncoded);
        // 把公钥和私钥保存到根目录
        FileUtils.writeStringToFile(new File(pubPath), publicEncodeString, Charset.forName(System.getProperty("file.encoding")));
        FileUtils.writeStringToFile(new File(priPath), privateEncodeString, Charset.forName(System.getProperty("file.encoding")));

    }
}